-
Notifications
You must be signed in to change notification settings - Fork 474
Fix resource leak in Maven plugin #571
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 9 commits
b695622
823c2c3
e4db705
e872905
aaddff8
cb724e9
5ca3b18
267767a
51647ab
5bf0896
2395492
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -17,20 +17,26 @@ | |
|
||
import static com.diffplug.spotless.MoreIterables.toNullHostileList; | ||
import static com.diffplug.spotless.MoreIterables.toSortedSet; | ||
import static java.util.Comparator.comparing; | ||
|
||
import java.io.File; | ||
import java.io.FileInputStream; | ||
import java.io.IOException; | ||
import java.io.InputStream; | ||
import java.io.Serializable; | ||
import java.security.MessageDigest; | ||
import java.util.Arrays; | ||
import java.util.Collection; | ||
import java.util.Collections; | ||
import java.util.HashMap; | ||
import java.util.List; | ||
import java.util.Map; | ||
|
||
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; | ||
|
||
/** Computes a signature for any needed files. */ | ||
public final class FileSignature implements Serializable { | ||
private static final long serialVersionUID = 1L; | ||
private static final long serialVersionUID = 2L; | ||
|
||
/* | ||
* Transient because not needed to uniquely identify a FileSignature instance, and also because | ||
|
@@ -39,10 +45,7 @@ public final class FileSignature implements Serializable { | |
*/ | ||
@SuppressFBWarnings("SE_TRANSIENT_FIELD_NOT_RESTORED") | ||
private final transient List<File> files; | ||
|
||
private final String[] filenames; | ||
private final long[] filesizes; | ||
private final long[] lastModified; | ||
private final Sig[] signatures; | ||
|
||
/** Method has been renamed to {@link FileSignature#signAsSet}. | ||
* In case no sorting and removal of duplicates is required, | ||
|
@@ -77,21 +80,30 @@ public static FileSignature signAsSet(File... files) throws IOException { | |
|
||
/** Creates file signature insensitive to the order of the files. */ | ||
public static FileSignature signAsSet(Iterable<File> files) throws IOException { | ||
return new FileSignature(toSortedSet(files)); | ||
List<File> natural = toSortedSet(files); | ||
List<File> onNameOnly = toSortedSet(files, comparing(File::getName)); | ||
if (natural.size() != onNameOnly.size()) { | ||
StringBuilder builder = new StringBuilder(); | ||
builder.append("For these files:\n"); | ||
for (File file : files) { | ||
builder.append(" " + file.getAbsolutePath() + "\n"); | ||
} | ||
builder.append("a caching signature is being generated, which will be based only on their\n"); | ||
builder.append("names, not their full path (foo.txt, not C:\folder\foo.txt). Unexpectedly,\n"); | ||
builder.append("you have two files with different paths, but the same names. You must\n"); | ||
builder.append("rename one of them so that all files have unique names."); | ||
throw new IllegalArgumentException(builder.toString()); | ||
} | ||
return new FileSignature(onNameOnly); | ||
} | ||
|
||
private FileSignature(final List<File> files) throws IOException { | ||
this.files = files; | ||
|
||
filenames = new String[this.files.size()]; | ||
filesizes = new long[this.files.size()]; | ||
lastModified = new long[this.files.size()]; | ||
this.files = validateInputFiles(files); | ||
this.signatures = new Sig[this.files.size()]; | ||
|
||
int i = 0; | ||
for (File file : this.files) { | ||
filenames[i] = file.getCanonicalPath(); | ||
filesizes[i] = file.length(); | ||
lastModified[i] = file.lastModified(); | ||
signatures[i] = cache.sign(file); | ||
++i; | ||
} | ||
} | ||
|
@@ -120,6 +132,74 @@ public static String pathUnixToNative(String pathUnix) { | |
return LineEnding.nativeIsWin() ? pathUnix.replace('/', '\\') : pathUnix; | ||
} | ||
|
||
private static List<File> validateInputFiles(List<File> files) { | ||
for (File file : files) { | ||
if (!file.isFile()) { | ||
throw new IllegalArgumentException( | ||
"File signature can only be created for existing regular files, given: " | ||
+ file); | ||
} | ||
} | ||
return files; | ||
} | ||
|
||
/** | ||
* It is very common for a given set of files to be "signed" many times. For example, | ||
* the jars which constitute any given formatter live in a central cache, but will be signed | ||
* over and over. To save this I/O, we maintain a cache, invalidated by lastModified time. | ||
*/ | ||
static final Cache cache = new Cache(); | ||
|
||
static class Cache { | ||
Map<String, Sig> cache = new HashMap<>(); | ||
|
||
synchronized Sig sign(File fileInput) throws IOException { | ||
String canonicalPath = fileInput.getCanonicalPath(); | ||
Sig sig = cache.computeIfAbsent(canonicalPath, ThrowingEx.<String, Sig> wrap(p -> { | ||
MessageDigest digest = MessageDigest.getInstance("SHA-256"); | ||
File file = new File(p); | ||
// calculate the size and content hash of the file | ||
long size = 0; | ||
byte[] buf = new byte[1024]; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just curious, why do you prefer to read the file this way instead of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Some of the eclipse jars push into the tens of MB, and SHA-256 only works on 512 bytes at a time. It's a micro-optimization to worry about that memory pressure, very possible that it's slower than |
||
long lastModified; | ||
try (InputStream input = new FileInputStream(file)) { | ||
lastModified = file.lastModified(); | ||
int numRead; | ||
while ((numRead = input.read(buf)) != -1) { | ||
size += numRead; | ||
digest.update(buf, 0, numRead); | ||
} | ||
} | ||
return new Sig(file.getName(), size, digest.digest(), lastModified); | ||
})); | ||
long lastModified = fileInput.lastModified(); | ||
if (sig.lastModified != lastModified) { | ||
cache.remove(canonicalPath); | ||
return sign(fileInput); | ||
} else { | ||
return sig; | ||
} | ||
} | ||
} | ||
|
||
@SuppressFBWarnings("SE_TRANSIENT_FIELD_NOT_RESTORED") | ||
static class Sig implements Serializable { | ||
private static final long serialVersionUID = 6727302747168655222L; | ||
|
||
final String name; | ||
final long size; | ||
final byte[] hash; | ||
/** transient because state should be transferable from machine to machine. */ | ||
final transient long lastModified; | ||
|
||
Sig(String name, long size, byte[] hash, long lastModified) { | ||
this.name = name; | ||
this.size = size; | ||
this.hash = hash; | ||
this.lastModified = lastModified; | ||
} | ||
} | ||
|
||
/** Asserts that child is a subpath of root. and returns the subpath. */ | ||
public static String subpath(String root, String child) { | ||
if (child.startsWith(root)) { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
/* | ||
* Copyright 2020 DiffPlug | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
package com.diffplug.spotless.npm; | ||
|
||
import java.io.File; | ||
|
||
class NodeServerLayout { | ||
|
||
private final File nodeModulesDir; | ||
private final File packageJsonFile; | ||
private final File serveJsFile; | ||
|
||
NodeServerLayout(File buildDir, String stepName) { | ||
this.nodeModulesDir = new File(buildDir, "spotless-node-modules-" + stepName); | ||
this.packageJsonFile = new File(nodeModulesDir, "package.json"); | ||
this.serveJsFile = new File(nodeModulesDir, "serve.js"); | ||
} | ||
|
||
File nodeModulesDir() { | ||
return nodeModulesDir; | ||
} | ||
|
||
File packageJsonFile() { | ||
return packageJsonFile; | ||
} | ||
|
||
File serveJsFile() { | ||
return serveJsFile; | ||
} | ||
} |
Uh oh!
There was an error while loading. Please reload this page.